#include "config.h"

#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>
#include <dirent.h>
#include <ctype.h>

#define DBG_SUBSYS S_LIBINTERFACE

#include "configure.h"
#include "regex.h"
#include "env.h"
#include "adt.h"
#include "net_table.h"
#include "cluster.h"
#include "lich_md.h"
#include "lichstor.h"
#include "license.h"
#include "md_map.h"
#include "lichstor.h"
#include "../../storage/storage/vnode.h"
#include "net_global.h"
#include "utils.h"
#include "dbg.h"
#include "../../storage/controller/volume_proto.h"
#include "cJSON.h"

typedef enum {
        OP_NULL,
        OP_SET,
        OP_LIST,
        OP_GET,
        OP_REMOVE,
} admin_op_t;

int object_md5sum(const fileid_t *oid);

static void usage()
{
        fprintf(stderr, "\nusage:\n"
                        "lich.attr --get key <path>\n"
                        "lich.attr --list <path>\n"
                        "lich.attr --set key --value value  <path>\n"
                        "lich.attr --remove key <path>\n"
               );
}

static int __valid_ip(const char *ipstr)
{
        int ret;
        regex_t reg;
        regmatch_t match;
        char errorbuf[MAX_BUF_LEN];
        const char *regstr = "^(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\\."
                "(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\."
                "(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\."
                "(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)$";

        ret = regcomp(&reg, regstr, REG_EXTENDED);
        if (unlikely(ret)) {
                regerror(ret, &reg, errorbuf, MAX_BUF_LEN);
                DERROR("%s %d\n", errorbuf, ret);
                GOTO(err_ret, ret);
        }

        ret = regexec(&reg, ipstr, 1, &match, 0);
        if (unlikely(ret)) {
                if (ret == REG_NOMATCH) {
                        ret = ENOENT;
                        DINFO("key %s buf %s\n", regstr, ipstr);
                        GOTO(err_free,ret);
                } else {
                        regerror(ret, &reg, errorbuf, MAX_BUF_LEN);
                        DERROR("%s %d\n", errorbuf, ret);
                        GOTO(err_free, ret);
                }
        }

        regfree(&reg);

        return 1;
err_free:
        regfree(&reg);
err_ret:
        return 0;
}

static int lich_xattr_check_key(const char *pool, const char *path, const char *key, const char *value)
{
        int ret, num, i;
        char tmp[MAX_INFO_LEN], *tmp2[128];
        uint64_t size = 0;

        if (key == NULL || *key == '\0' || path == NULL || *path == '\0' || value == NULL || *value == '\0') {
                usage();
                EXIT(EINVAL);
        }

        if (strcmp(key, LICH_SYSTEM_ATTR_PASSWORD) == 0) {
                if (strlen(value) < 12 || strlen(value) > 16) {
                        DERROR("\x1b[1;31m%s length must between 12 and 16\x1b[0m\n", LICH_SYSTEM_ATTR_PASSWORD);
                        ret = EINVAL;
                        GOTO(err_ret, ret);
                }
        } else if (strcmp(key, LICH_SYSTEM_ATTR_USERNAME) == 0) {
                if (strlen(value) >= MAX_NAME_LEN) {
                        ret = EINVAL;
                        DERROR("The value is the number of bytes in the range of 1 to %d.\n", MAX_NAME_LEN - 1);
                        GOTO(err_ret, ret);
                }
        } else if (strcmp(key, LICH_SYSTEM_ATTR_CREATETIME) == 0 ||
                        strcmp(key, LICH_SYSTEM_ATTR_EC) == 0) {
                ret = EPERM;
                GOTO(err_ret, ret);
        } else if (strcmp(key, STORAGE_AREA_KEY) == 0) {
                if (cluster_storage_area_is_null(value)) {
                } else {
                        ret = dispatch_check_storage_area(value);
                        if (ret)
                                GOTO(err_ret, ret);
                }
        } else if (strcmp(key, LICH_SYSTEM_ATTR_SPARE_WARN) == 0 ||
                        strcmp(key, LICH_SYSTEM_ATTR_SPARE_ERROR) == 0) {

                ret = spare_value2size(pool, value, &size);
                if (ret)
                        GOTO(err_ret, ret);

                if (size == 0) {
                        ret = EINVAL;
                        GOTO(err_ret, ret);
                }
        } else if (strcmp(key, THROT_IOPS) == 0) {
                strncpy(tmp, value, MAX_INFO_LEN);
                num = 3;
                _str_split(tmp, ',', tmp2, &num);

                for (i = 0; i < num; i++) {
                        if (strchr(tmp2[i], ',')) {
                                printf("\x1b[1;31m%s up to 3 items can be set\x1b[0m\n", THROT_IOPS);
                                ret = EINVAL;
                                GOTO(err_ret, ret);
                        }

                        if (!_str_isdigit(tmp2[i])) {
                                printf("\x1b[1;31m%s is not digital\x1b[0m\n", tmp2[i]);
                                ret = EINVAL;
                                GOTO(err_ret, ret);
                        }
                }
        } else if (strcmp(key, THROT_BW) == 0) {
                strncpy(tmp, value, MAX_INFO_LEN);
                num = 3;
                _str_split(tmp, ',', tmp2, &num);

                for (i = 0; i < num; i++) {
                        if (strchr(tmp2[i], ',')) {
                                printf("\x1b[1;31m%s up to 3 items can be set\x1b[0m\n", THROT_BW);
                                ret = EINVAL;
                                GOTO(err_ret, ret);
                        }

                        if (!_str_isdigit(tmp2[i])) {
                                if (_str_lastchr(tmp2[i]) != 'K' && _str_lastchr(tmp2[i]) != 'k') {
                                        printf("\x1b[1;31m%s is not digital, unit only allow 'k' and 'K'\x1b[0m\n", tmp2[i]);
                                        ret = EINVAL;
                                        GOTO(err_ret, ret);
                                }
                        }
                }
        } else if (strcmp(key, LICH_SYSTEM_ATTR_IP) == 0) {
                strncpy(tmp, value, MAX_INFO_LEN);
                num = 128;
                _str_split(tmp, '&', tmp2, &num);
                for (i = 0; i < num; i++) {
                        if (!__valid_ip(tmp2[i])){
                                printf("\x1b[1;31m%s is not invalid ip \x1b[0m\n", tmp2[i]);
                                ret = EINVAL;
                                GOTO(err_ret, ret);
                        }
                }
        }

        return 0;
err_ret:
        return ret;
}

int lich_xattr_set(const char *pool, const char *path, const char *key, const char *value)
{
        int ret;
        fileid_t fileid;
        setattr_t setattr;

        ret = lich_xattr_check_key(pool, path, key, value);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        DBUG("set %s %s %s\n", key, value, path);

        ret = stor_lookup1(pool, path, &fileid);
        if (unlikely(ret)) {
                GOTO(err_ret, ret);
        }

        memset(&setattr, 0x0, sizeof(setattr));
        if (strcmp(key, LICH_SYSTEM_ATTR_REPNUM) == 0
                        || strcmp(key, "repnum") == 0)  {
                ret = stor_set_repnum(pool, &fileid, atoi(value));
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        } else if (strcmp(key, LICH_SYSTEM_ATTR_WRITEBACK) == 0
                        || strcmp(key, "writeback") == 0)  {
                setattr.writeback.set_it = 1;
                setattr.writeback.val = atoi(value);
                ret = md_setattr(&fileid, NULL, &setattr);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        } else if (strcmp(key, LICH_SYSTEM_ATTR_LOCALIZE) == 0
                        || strcmp(key, "localize") == 0)  {
                setattr.localize.set_it = 1;
                setattr.localize.val = atoi(value);
                ret = md_setattr(&fileid, NULL, &setattr);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        } else if (strcmp(key, LICH_SYSTEM_ATTR_MULTIPATH) == 0
                        || strcmp(key, "multipath") == 0)  {
                setattr.multpath.set_it = 1;
                setattr.multpath.val = atoi(value);
                ret = md_setattr(&fileid, NULL, &setattr);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        } else if (strcmp(key, LICH_SYSTEM_ATTR_PRIORITY) == 0
                        || strcmp(key, "priority") == 0)  {
                DWARN("Temporarily not supported !!!\n");
                return 0;
                setattr.priority.set_it = 1;
                setattr.priority.val = atoi(value);
                ret = md_setattr(&fileid, NULL, &setattr);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        } else if (strcmp(key, LICH_SYSTEM_ATTR_EC) == 0
                        || strcmp(key, "ec") == 0)  {
                uint32_t k = 0, r = 0;

                if (fileid.type != __POOL_CHUNK__) {
                        ret = EPERM;
                        GOTO(err_ret, ret);
                }

                ret = sscanf(value, "%u+%u", &k, &r);
                if (ret != 2 || !k || !r) {
                        ret = EINVAL;
                        GOTO(err_ret, ret);
                }

                setattr.ec.set_it = 1;
                setattr.ec.ec.plugin = PLGUIN_EC_ISA;
                setattr.ec.ec.tech = TECH_ISA_SSE;
                setattr.ec.ec.m = k + r;
                setattr.ec.ec.k = k;

                ret = md_setattr(&fileid, NULL, &setattr);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        } else if (strcmp(key, INITIATOR) == 0
                        || strcmp(key, LICH_SYSTEM_ATTR_AUTH) == 0) {
                ret = md_removeattr_extend(&fileid, key);
                if (unlikely(ret)) {
                        if (ret != ENOKEY)
                                GOTO(err_ret, ret);
                }
                ret = md_setattr_extend(&fileid, key, value);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        } else {
                ret = md_xattr_set(NULL, &fileid, key, value, strlen(value), 0);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }

        return 0;
err_ret:
        return ret;
}

int lich_xattr_remove(const char *pool, const char *path, const char *key)
{
        int ret;
        fileid_t fileid;
        nid_t nid;

        if (key == NULL || path == NULL) {
                usage();
                EXIT(EINVAL);
        }

        ret = stor_lookup1(pool, path, &fileid);
        if (unlikely(ret)) {
                GOTO(err_ret, ret);
        }

        ret = md_map_getsrv(&fileid, &nid);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        if (strcmp(key, INITIATOR) == 0
                        || strcmp(key, LICH_SYSTEM_ATTR_AUTH) == 0) {
                ret = md_removeattr_extend(&fileid, key);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        } else {
                ret = md_xattr_remove(&nid, &fileid, key);
                if (unlikely(ret)) {
                        GOTO(err_ret, ret);
                }
        }

        return 0;
err_ret:
        return ret;
}

#define SHOW_FILEINFO_ATTR 1

int lich_xattr_list(const char *pool, const char *path)
{
        int ret, buflen = MAX_BUF_LEN;
        char buf[MAX_BUF_LEN];
        fileid_t fileid;
        nid_t nid;
#if SHOW_FILEINFO_ATTR
        fileinfo_t fileinfo;
#endif

        if (path == NULL) {
                usage();
                EXIT(EINVAL);
        }

        ret = stor_lookup1(pool, path, &fileid);
        if (unlikely(ret)) {
                GOTO(err_ret, ret);
        }

#if SHOW_FILEINFO_ATTR
        ret = md_getattr(&fileid, &fileinfo);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        //      printf("chkid : "CHKID_FORMAT"\n", CHKID_ARG(&fileid));
        //      printf("repnum: %d\n", fileinfo.repnum);

        if ((int)fileinfo.priority == -1)
                printf("priority : default\n");
        else
                printf("priority : %d\n", fileinfo.priority);

        if ((fileinfo.attr & __FILE_ATTR_LOCALIZE__) ==  __FILE_ATTR_LOCALIZE__)
                printf("localize : 1\n");
        else
                printf("localize : 0\n");

        if ((fileinfo.attr & __FILE_ATTR_MULTPATH__) ==  __FILE_ATTR_MULTPATH__)
                printf("multipath : 1\n");
        else
                printf("multipath : 0\n");

        if ((fileinfo.attr & __FILE_ATTR_PROTECT__) ==  __FILE_ATTR_PROTECT__)
                printf("protect : 1\n");
        else
                printf("protect : 0\n");

        if ((fileinfo.attr & __FILE_ATTR_SNAPSHOT__) ==  __FILE_ATTR_SNAPSHOT__)
                printf("snapshot : 1\n");

        if ((fileinfo.attr & __FILE_ATTR_CLONE__) ==  __FILE_ATTR_CLONE__)
                printf("clone : 1\n");
#endif

        ret = md_map_getsrv(&fileid, &nid);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        buf[0] = '\0';
        ret = md_xattr_list(&nid, &fileid, buf, &buflen);
        if (unlikely(ret)) {
                GOTO(err_ret, ret);
        }

        printf("%s", buf);

        return 0;
err_ret:
        return ret;
}

int lich_xattr_get_buf(const char *pool, const char *path, const char *key, char *buf, int *buflen)
{
        int ret;
        fileid_t fileid;
        fileinfo_t fileinfo;
        nid_t nid;

        *buflen = 0;

        if (key == NULL || path == NULL) {
                usage();
                EXIT(EINVAL);
        }

        ret = stor_lookup1(pool, path, &fileid);
        if (unlikely(ret)) {
                GOTO(err_ret, ret);
        }

        ret = md_getattr(&fileid, &fileinfo);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        if (strcmp(key, LICH_SYSTEM_ATTR_REPNUM) == 0
                        || strcmp(key, "repnum") == 0)  {
                snprintf(buf, MAX_BUF_LEN, "%d/%d", fileinfo.repnum_usr, fileinfo.repnum_sys);
                *buflen = strlen(buf);
        } else if (strcmp(key, LICH_SYSTEM_ATTR_WRITEBACK) == 0
                        || strcmp(key, "writeback") == 0)  {
                snprintf(buf, MAX_BUF_LEN, "%d", (fileinfo.attr & __FILE_ATTR_WRITEBACK__)
                                == __FILE_ATTR_WRITEBACK__);
                *buflen = strlen(buf);
        } else if (strcmp(key, LICH_SYSTEM_ATTR_LOCALIZE) == 0
                        || strcmp(key, "localize") == 0)  {
                snprintf(buf, MAX_BUF_LEN, "%d", (fileinfo.attr & __FILE_ATTR_LOCALIZE__)
                                == __FILE_ATTR_LOCALIZE__);
                *buflen = strlen(buf);
        } else if (strcmp(key, LICH_SYSTEM_ATTR_MULTIPATH) == 0
                        || strcmp(key, "multipath") == 0)  {
                snprintf(buf, MAX_BUF_LEN, "%d", (fileinfo.attr & __FILE_ATTR_MULTPATH__)
                                == __FILE_ATTR_MULTPATH__);
                *buflen = strlen(buf);
        } else if (strcmp(key, LICH_SYSTEM_ATTR_PRIORITY) == 0
                        || strcmp(key, "priority") == 0)  {
                snprintf(buf, MAX_BUF_LEN, "%d", fileinfo.priority);
                *buflen = strlen(buf);
        } else if (strcmp(key, LICH_SYSTEM_ATTR_EC) == 0
                        || strcmp(key, "ec") == 0)  {
                if (fileinfo.ec.plugin == PLUGIN_NULL)
                        snprintf(buf, MAX_BUF_LEN, "replica");
                else
                        snprintf(buf, MAX_BUF_LEN, "%u+%u", fileinfo.ec.k, fileinfo.ec.m - fileinfo.ec.k);
                *buflen = strlen(buf);
        } else if (strcmp(key, INITIATOR) == 0
                        || strcmp(key, LICH_SYSTEM_ATTR_AUTH) == 0) {
                ret = md_getattr_extend(&fileid, key, buf);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
                *buflen = strlen(buf);
        } else {
                *buflen = MAX_BUF_LEN;

                ret = md_map_getsrv(&fileid, &nid);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                ret = md_xattr_get(&nid, &fileid, key, buf, buflen);
                if (unlikely(ret)) {
                        GOTO(err_ret, ret);
                }
        }

        return 0;
err_ret:
        return ret;
}

int lich_xattr_get(const char *pool, const char *path, const char *key, int output_format)
{
        int ret, buflen;
        char *buf = NULL;

        ret = ymalloc((void **)&buf, BUF_128);
        if (unlikely(ret)) {
                GOTO(err_ret, ret);
        }

        memset(buf, 0x00, BUF_128);
        ret = lich_xattr_get_buf(pool, path, key, buf, &buflen);
        if (unlikely(ret)) {
                GOTO(err_free, ret);
        }

        if (output_format == JSON_FORMAT) {
                cJSON *json = cJSON_CreateObject();
                cJSON_AddStringToObject(json, key, buf);
                printf("%s\n", cJSON_PrintUnformatted(json));
                cJSON_Delete(json);
        } else {
                printf("%s\n", buf);
        }

        yfree((void **)&buf);
        return 0;
err_free:
        yfree((void **)&buf);
err_ret:
        return ret;
}

#if 0
#define attr_for_each(buf, buflen, de, off)                      \
        YASSERT(sizeof(off) == sizeof(uint64_t));               \
for (de = (void *)(buf);                                \
                (void *)de < (void *)(buf) + buflen ;              \
                off = de->d_off, de = (void *)de + de->d_reclen)

static int __lich_attr_list(const char *path)
{
        int ret;
        attrlist_t *list;

        ret = ENOSYS;
        GOTO(err_ret, ret);

        if (path == NULL) {
                usage();
                EXIT(EINVAL);
        }

        while (1) {
                ret = storage_attr_list(path, key, strlen(value));
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                attr_for_each () {
                }
        }

        return 0;
err_ret:
        return ret;
}

#endif


#if 0
int main(int argc, char *argv[])
{
        int ret, op = OP_NULL;
        char c_opt;
        int verbose = 0;
        const char *key = NULL, *value = NULL;

        dbg_info(0);

        (void) verbose;

        while (srv_running) {
                int option_index = 0;

                static struct option long_options[] = {
                        { "set", required_argument, 0, 's'},
                        { "get", required_argument, 0, 'g'},
                        { "value", required_argument, 0, 'V'},
                        { "list", required_argument, 0, 'l'},
                        { "remove", required_argument, 0, 'r'},
                        { "verbose", 0, 0, 'v' },
                        { "help",    0, 0, 'h' },
                        { 0, 0, 0, 0 },
                };

                c_opt = getopt_long(argc, argv, "c:l:eas:mr:vh", long_options, &option_index);
                if (c_opt == -1)
                        break;

                switch (c_opt) {
                case 0:
                        switch (option_index) {
                        default:
                                fprintf(stderr, "Hoops, wrong op got!\n");
                                YASSERT(0); 
                        }

                        break;
                case 's':
                        op = OP_SET;
                        key = optarg;
                        break;
                case 'g':
                        op = OP_GET;
                        key = optarg;
                        break;
                case 'V':
                        value = optarg;
                        break;
                case 'l':
                        DBUG("list\n");
                        op = OP_LIST;
                        key = optarg;
                        break;
                case 'r':
                        op = OP_REMOVE;
                        key = optarg;
                        break;
                case 'v':
                        verbose = 1;
                        break;
                case 'h':
                        usage();
                        EXIT(0);
                default:
                        usage();
                        EXIT(EINVAL);
                }
        }

#if 1
        if (argc == 1) {
                usage();
                EXIT(EINVAL);
        }
#endif

        ret = env_init_simple("lich.attr");
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = network_connect_master();
        if (unlikely(ret)) {
                GOTO(err_ret, ret);
        }

        ret = stor_init(NULL, -1);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        if((op == OP_SET) || (op == OP_REMOVE)) {
                ret = storage_license_check();
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }

        //DBUG("type %d op %d\n", type, op);

        //fence_test();

        switch (op) {
        case OP_SET:
                ret = __lich_xattr_set(argv[argc - 1], key, value);
                if (unlikely(ret)) {
                        GOTO(err_ret, ret);
                }

                break;
        case OP_GET:
                ret = __lich_xattr_get(argv[argc - 1], key);
                if (unlikely(ret)) {
                        GOTO(err_ret, ret);
                }

                break;
        case OP_REMOVE:
                ret = __lich_xattr_remove(argv[argc - 1], key);
                if (unlikely(ret)) {
                        GOTO(err_ret, ret);
                }

                break;
        default:
                usage();
                EXIT(EINVAL);
        }

        return 0;
err_ret:
        EXIT(_errno(ret));
}

#endif
